﻿/* Copyright (c) Microsoft Corporation
 * 
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.  You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * 
 * THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
 * 
 * See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ProximityTapper
{
    /// <summary>
    /// Class for AppInfoSubMessage
    /// </summary>
    public class AppInfoSubMessage : IAppInfoSubMessage
    {
        // dynamic byte array to store platform qualifier
        public byte[] PlatformQualifier { get; private set; }

        // dynamic byte array to store application identifier
        public byte[] ApplicationIdentifier { get; private set; }

        /// <summary>
        /// Try parsing AppInfo sub message
        /// </summary>
        /// <param name="data">Message Data</param>
        /// <param name="offset">
        /// Starting / current position in byte array.
        /// This is updated as the bytes are read.
        /// </param>
        /// <returns>true is successful in parsing, false otherwise</returns>
        /// <summary>
        bool IAppInfoSubMessage.TryParse(byte[] data, ref UInt32 offset)
        {
            bool isParsed = true;

            try
            {
                // Check for PlatformQualifier
                if ((data.Length - offset) > 0x0)
                {
                    byte platformQualifierSize = data[offset];
                    offset++;

                    if (platformQualifierSize <= (data.Length - offset))
                    {
                        this.PlatformQualifier = new byte[platformQualifierSize];
                        Array.Copy(data, offset, this.PlatformQualifier, 0, platformQualifierSize);
                        offset += platformQualifierSize;
                    }
                    else
                    {
                        // Parsing error
                        isParsed = false;
                    }
                }

                // Check for application identifier
                // This condition would fail if previous one failed
                if ((data.Length - offset) > 0x0)
                {
                    byte applicationIdentifierSize = data[offset];
                    offset++;

                    // Confirm that we have enough bytes to read
                    if (applicationIdentifierSize <= (data.Length - offset))
                    {
                        this.ApplicationIdentifier = new byte[applicationIdentifierSize];
                        Array.Copy(data, offset, this.ApplicationIdentifier, 0, applicationIdentifierSize);
                        offset += applicationIdentifierSize;
                    }
                    else
                    {
                        // Parsing error
                        isParsed = false;
                    }
                }
                else
                {
                    // Parsing error
                    isParsed = false;
                }
            }
            catch (Exception)
            {
                // Parsing error
                isParsed = false;
            }

            return isParsed;
        }

        public override string ToString()
        {
            String output = "\n\nApplication Info";
            ASCIIEncoding encoder = new ASCIIEncoding();

            if (this.PlatformQualifier != null)
            {
                output += "\nPlatform Qualifier Size : " + StringFormatter.ToHexString(this.PlatformQualifier.Length);
                output += "\nPlatform Qualifier : " + encoder.GetString(this.PlatformQualifier);
            }
            else
            {
                output += "\nPlatform Qualifier Size : 0x0";
            }

            if (this.ApplicationIdentifier != null)
            {
                output += "\nApplication Identifier Size : " + StringFormatter.ToHexString(this.ApplicationIdentifier.Length);
                output += "\nApplication Identifier : " + encoder.GetString(this.ApplicationIdentifier);
            }
            else
            {
                output += "\nApplication Identifier Size : 0x0";
            }

            return output;
        }
    }

    /// <summary>
    /// Class for SessionFactoryServiceActivation
    /// </summary>
    [CLSCompliant(false)]
    public class SessionFactoryServiceActivation : ISessionFactoryServiceActivation, IMessageData
    {
        private static UInt32 MAX_ID = 8;
        private static UInt32 SESSION_FACTORY_SERVICE_ACTIVATION_MIN_SIZE = MAX_ID + sizeof(UInt32) + 4 * sizeof(byte) + sizeof(UInt16); // Not counting a byte of L

        // Service Activation Header
        private IServiceActivationHeader serviceActivationHeader = new ServiceActivationHeader();

        public IServiceActivationHeader ServiceActivationHeader
        {
            get { return serviceActivationHeader; }
            set { serviceActivationHeader = value; }
        }

        // byte array to store Reply Channel ID (8 bytes)
        private byte[] replyChannelId = new byte[MAX_ID];

        public byte[] ReplyChannelId
        {
            get { return replyChannelId; }
            set { replyChannelId = value; }
        }

        // bool to display if device preference is to act as client or server
        // Determined from ClientPreference (See below)
        public bool IsClient { get; private set; }

        // Client preference (32 bits)
        public UInt32 ClientPreference { get; private set; }

        // Reserved word (7 bits)
        public byte Reserved { get; private set; }

        // L bit (1 bit)
        public byte L { get; private set; }

        // Reserved word 2 (24 bits)
        public UInt32 Reserved2 { get; private set; }

        // List of app info
        private List<IAppInfoSubMessage> appInfoList = new List<IAppInfoSubMessage>();

        public List<IAppInfoSubMessage> AppInfoList
        {
            get { return appInfoList; }
            set { appInfoList = value; }
        }

        /// <summary>
        /// Function to try parse SessionFactoryServiceActivation data
        /// </summary>
        /// <param name="data">Message Data</param>
        /// <returns>true is successful in parsing, false otherwise</returns>
        bool IMessageData.TryParse(byte[] data)
        {
            bool isParsed = true;
            UInt32 offset = 0;

            try
            {
                if (!this.ServiceActivationHeader.TryParse(data, ref offset))
                {
                    isParsed = false;
                }

                // Verify that remaining data length is atleast minimum size 
                // required by Service Descriptor Entry Header
                if (SESSION_FACTORY_SERVICE_ACTIVATION_MIN_SIZE <= (data.Length - offset))
                {
                    Array.Copy(data, offset, this.ReplyChannelId, 0, MAX_ID);
                    offset += MAX_ID;

                    this.ClientPreference = BigEndianConverter.ToUInt32(data, offset);
                    offset += sizeof(UInt32);

                    this.IsClient = (this.ClientPreference > 0x1000) ? true : false;

                    this.Reserved = (byte)((data[offset] & 0xFE) >> 1);
                    this.L = (byte)(data[offset] & 0x1);
                    offset++;

                    // Reserved2 is 24 bits long
                    this.Reserved2 = (UInt32)((UInt32)data[offset] << 16);
                    offset++;
                    this.Reserved2 |= (UInt32)((UInt32)data[offset] << 8);
                    offset++;
                    this.Reserved2 |= (UInt32)data[offset];
                    offset++;

                    // Get AppInfo count
                    UInt16 appInfoCount = BigEndianConverter.ToUInt16(data, offset);
                    offset += sizeof(UInt16);

                    for (UInt16 counter = appInfoCount; counter > 0; counter--)
                    {
                        IAppInfoSubMessage iAppInfoMessage = new AppInfoSubMessage();

                        // Try Parse appinfo
                        if (!iAppInfoMessage.TryParse(data, ref offset))
                        {
                            // Parsing error
                            isParsed = false;
                            break;
                        }

                        // Add to list if successfully parsed
                        this.AppInfoList.Add(iAppInfoMessage);
                    }
                }
                else
                {
                    // Parsing error
                    isParsed = false;
                }
            }
            catch (Exception)
            {
                // Parsing error
                isParsed = false;
            }

            // Make sure that all bytes are read
            if (offset != data.Length)
            {
                isParsed = false;
            }

            return isParsed;
        }

        public override string ToString()
        {
            String output = "Message Type : Session Factory Service Activation";

            output += this.ServiceActivationHeader.ToString();

            output += "\nReply Channel ID : " + Convert.ToBase64String(this.ReplyChannelId);

            output += "\nClientPreference : " + ((this.IsClient) ? "Client" : "Server") + " (" + StringFormatter.ToHexString(this.ClientPreference) + ")";

            output += "\nReserved Word : " + StringFormatter.ToHexString(this.Reserved);
            output += "\nLaunch Flag : " + StringFormatter.ToHexString(this.L);
            output += "\nReserved Word 2 : " + StringFormatter.ToHexString(this.Reserved2);

            output += "\nApp Info Count : " + StringFormatter.ToHexString(this.AppInfoList.Count);

            foreach (AppInfoSubMessage appInfo in this.AppInfoList)
            {
                output += appInfo.ToString();
            }

            return output;
        }
    }
}
